package poker

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/heroiclabs/nakama-common/runtime"
	"github.com/mitchellh/mapstructure"
	"strconv"
	"time"
)

type Match struct {
	Deck []Card
}

func NewPokerMatch(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule) (m runtime.Match, err error) {
	return &Match{}, nil
}

type GameStage string

const (
	StageWaiting  GameStage = "waiting"  // 等待
	StageReady    GameStage = "ready"    // 房主开始游戏
	StageDealing  GameStage = "dealing"  // 发手牌阶段
	StagePreFlop  GameStage = "pre_flop" // 第一轮下注（无公共牌）
	StageFlop     GameStage = "flop"     // 发3张公共牌 + 下注
	StageTurn     GameStage = "turn"     // 发第4张牌 + 下注
	StageRiver    GameStage = "river"    // 发第5张牌 + 下注
	StageShowdown GameStage = "showdown" // 摊牌、结算
	StageFinished GameStage = "finished" // 一局结束，等待下一局
)

type MatchState struct {
	Players        map[string]*Player `json:"players"`        // 房间内在线玩家
	SeatedPlayers  map[int64]*Player  `json:"seatedPlayers"`  // 已入座玩家，按 seat 编号索引
	InitialBuyIn   int64              `json:"initialBuyIn"`   // 首次买入
	SmallBlind     int64              `json:"smallBlind"`     // 小盲
	BigBlind       int64              `json:"bigBlind"`       // 大盲
	MaxBuyIn       int64              `json:"maxBuyIn"`       // 最大买入
	MaxPlayerCount int64              `json:"maxPlayerCount"` // 最大玩家数量
	DismissAt      time.Time          `json:"dismissAt"`      // 解散时间
	RoomName       string             `json:"roomName"`       // 房间名
	Stage          GameStage          `json:"stage"`          // 游戏阶段
	CreatorId      string             `json:"creatorId"`      // 房主ID

	CommunityCards []Card `json:"communityCards"` // 公共牌
	Pot            int64  `json:"pot"`            // 奖池金额

	DealerPos  int64 `json:"dealerPos"`  // 庄家位置
	CurrentPos int64 `json:"currentPos"` // 当前行动玩家位置

	CurrentBet      int64 `json:"currentBet"` // 当前注额
	LastRaiseAmount int64
}

func (m *Match) MatchInit(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, params map[string]interface{}) (interface{}, int, string) {
	createPokerModel := CreatePokerRPCModel{}
	err := mapstructure.Decode(params, &createPokerModel)
	if err != nil {
		return nil, 0, ""
	}
	initialBuyIn := createPokerModel.InitialBuyIn
	smallBlind := initialBuyIn / 200
	bigBlind := initialBuyIn / 100
	state := &MatchState{
		Players:        make(map[string]*Player),
		SeatedPlayers:  make(map[int64]*Player),
		InitialBuyIn:   initialBuyIn,
		SmallBlind:     smallBlind,
		BigBlind:       bigBlind,
		MaxPlayerCount: createPokerModel.MaxPlayerCount,
		DismissAt:      time.Now().Add(time.Duration(createPokerModel.DismissAfterSEC)),
		RoomName:       createPokerModel.RoomName,
		MaxBuyIn:       createPokerModel.MaxBuyIn,
		Stage:          StageWaiting,
		CreatorId:      createPokerModel.CreatorId,
	}

	tickRate := 1
	label := createPokerModel.RoomName

	return state, tickRate, label
}

func (m *Match) MatchJoinAttempt(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presence runtime.Presence, metadata map[string]string) (interface{}, bool, string) {
	accept := true
	//mState, _ := state.(*MatchState)

	return state, accept, ""
}

func (m *Match) MatchJoin(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
	mState, _ := state.(*MatchState)

	for _, p := range presences {
		mState.Players[p.GetUserId()] = NewPlayer(p)
	}

	return mState
}

func (m *Match) MatchLeave(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
	mState, _ := state.(*MatchState)

	for _, p := range presences {
		delete(mState.Players, p.GetUserId())
	}

	return mState
}

func (m *Match) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, messages []runtime.MatchData) interface{} {
	mState, _ := state.(*MatchState)

	for _, msg := range messages {

		switch msg.GetOpCode() {
		case OpSyncGameReq:
			m.handleSyncGame(ctx, logger, dispatcher, msg, mState)
			break
		case OpSeatDown: // 入座
			m.handleJoinSeat(ctx, logger, dispatcher, msg, mState)
			m.handleStartGame(ctx, logger, dispatcher, msg, mState)
			break
		case OpSeatUp: // 离座
			break
		case OpStartGame: // 游戏开始
			if msg.GetUserId() != mState.CreatorId {
				// 只有房主才可以开始游戏
				break
			}
			mState.Stage = StageReady
			// 广播游戏开始，等待玩家入座即开始
			m.broadcast(logger, dispatcher, OpGameStarted, &MatchDataStartGameS{})

			m.handleStartGame(ctx, logger, dispatcher, msg, mState)
			break
		case OpCall:
			m.handleCall(ctx, logger, dispatcher, msg, mState)
			break
		case OpCheck:
			m.handleCheck(ctx, logger, dispatcher, msg, mState)
			break
		case OpFold:
			m.handleFold(ctx, logger, dispatcher, msg, mState)
			break
		case OpRaise:
			m.handleRaise(ctx, logger, dispatcher, msg, mState)
			break
		case OpAllin:
			m.handleAllIn(ctx, logger, dispatcher, msg, mState)
			break
		default:
			break
		}
	}

	return mState
}

func (m *Match) MatchTerminate(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, graceSeconds int) interface{} {
	message := "Server shutting down in " + strconv.Itoa(graceSeconds) + " seconds."
	err := dispatcher.BroadcastMessage(2, []byte(message), []runtime.Presence{}, nil, true)
	if err != nil {
		return nil
	}

	return state
}

func (m *Match) MatchSignal(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, data string) (interface{}, string) {
	return state, "signal received: " + data
}

// handleSyncGame 同步游戏状态
func (m *Match) handleSyncGame(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {
	m.sendToPlayer(logger, dispatcher, message, OpSyncGameResp, state)
}

// handleJoinSeat 处理入座逻辑
func (m *Match) handleJoinSeat(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {
	var reqData MatchDataSitDownC
	if err := json.Unmarshal(message.GetData(), &reqData); err != nil {
		logger.Error("Unmarshal err: %v", err)
		return
	}
	seat := reqData.Seat
	if seat < 0 || seat > 8 {
		logger.Error("seat: %v is invalid", seat)
		return
	}

	// 检查是否被占用
	_, seatTaken := state.SeatedPlayers[seat]

	if seatTaken {
		logger.Error("seat:%v already taken", seat)
		return
	}

	uid := message.GetUserId()

	// 修改玩家参数：是否坐下、座位号、筹码
	player, ok := state.Players[uid]
	if !ok {
		logger.Error("current player is not exist")
		return
	}

	if player.Seated && seat != player.Seat {
		delete(state.SeatedPlayers, player.Seat)
	}

	player.Seated = true
	player.Seat = seat
	if player.Chips <= 0 { // 没有筹码就要买入
		player.Chips = state.InitialBuyIn
	}
	// 加入到已入座map中
	state.SeatedPlayers[seat] = player

	// 广播入座成功
	m.broadcast(
		logger,
		dispatcher,
		OpPlayerSeatedDown,
		&MatchDataSitDownS{
			Seat:  seat,
			Uid:   uid,
			Chips: player.Chips,
		},
	)
}

// handleStartGame 同步游戏状态
func (m *Match) handleStartGame(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {
	if state.Stage != StageReady {
		return // 游戏已开始或未准备，直接返回
	}

	if len(state.SeatedPlayers) <= 1 {
		return // 人数不足，至少需两人
	}

	// 进入发牌阶段
	state.Stage = StageDealing

	// 洗牌
	m.shuffledDeck()

	// 发手牌并设置状态
	for _, player := range state.SeatedPlayers {
		if player.Chips > 0 {
			player.Hand = m.drawCards(2)
			player.Status = PlayerStatusPlaying
		} else {
			player.Status = PlayerStatusIdle
		}
	}

	// 检查 dealer 是否有效，不合法就选下一个有效玩家
	dealer := state.DealerPos
	if p, ok := state.SeatedPlayers[dealer]; !ok || p.Status != PlayerStatusPlaying {
		dealer = m.nextActiveSeat(dealer, 1, state.SeatedPlayers)
		state.DealerPos = dealer
	}

	// 获取盲注座位和行动座位
	sbAmount := state.SmallBlind
	sbPos, bbPos, actPos := m.CalcBlindsPreFlop(dealer, state.SeatedPlayers)

	// 小盲下注
	if sbPlayer, ok := state.SeatedPlayers[sbPos]; ok {
		amount := m.bet(sbPlayer, sbAmount, ActionSmallBlind)
		state.Pot += amount
	}

	// 大盲下注
	if bbPlayer, ok := state.SeatedPlayers[bbPos]; ok {
		amount := m.bet(bbPlayer, sbAmount*2, ActionBigBlind)
		state.Pot += amount
		state.CurrentBet = amount
	}
	// 更新当前操作位、和当前下注额
	state.CurrentPos = actPos

	// 构建公共玩家视图（精简版）
	seatedPlayersLite := make([]PlayerGameStatus, 0, len(state.SeatedPlayers))
	for _, player := range state.SeatedPlayers {
		seatedPlayersLite = append(seatedPlayersLite, player.PlayerGameStatus)
	}

	// 向每位玩家单独发送手牌+公共信息
	for _, player := range state.Players {
		m.sendToPlayer(logger, dispatcher, player, OpGamePreFlop, &MatchDataPreFlopRoundS{
			Hand:         player.Hand, // 私人手牌
			DealerPos:    dealer,
			ActPos:       actPos,
			CallAmount:   state.CurrentBet - player.Bet,
			MinRaiseTo:   m.CalcMinRaiseTo(state),
			CanCheck:     player.Bet == state.CurrentBet,
			SeatedPlayer: seatedPlayersLite, // 公共视图（无手牌）
		})
	}
	// 更新状态为 PreFlop 轮
	state.Stage = StagePreFlop
}

// 玩家下注
func (m *Match) bet(player *Player, amount int64, action RoundAction) int64 {
	spent := Min(player.Chips, amount)
	player.Bet += spent
	player.Chips -= spent
	// 判断是否 All-in
	if player.Chips == 0 {
		player.Status = PlayerStatusAllIn
		player.Action = ActionAllIn
	} else {
		player.Action = action
	}
	return amount
}

func (m *Match) handleCall(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {
	uid := message.GetUserId()
	player, ok := state.Players[uid]
	if !ok || player.Status != PlayerStatusPlaying {
		return // 玩家不存在或不能操作
	}

	// 计算应补的 call 金额
	callAmount := state.CurrentBet - player.Bet
	if callAmount <= 0 {
		// 不能Call
		return
	}

	// 实际可投入
	amount := m.bet(player, callAmount, ActionCall)
	state.Pot += amount

	state.CurrentPos = m.findNextActingSeat(player.Seat, state.SeatedPlayers)
	nextPlayer := state.SeatedPlayers[state.CurrentPos]

	m.broadcast(logger, dispatcher, OpPlayerAction, &MatchDataPlayerActionS{
		ActionType:  ActionCall,
		ActorSeat:   player.Seat,
		SpentAmount: amount,
		MainPot:     state.Pot,
		SidePot:     0,
		NextSeat:    state.CurrentPos,
		CallAmount:  state.CurrentBet - nextPlayer.Bet,
		MinRaiseTo:  m.CalcMinRaiseTo(state),
		CanCheck:    nextPlayer.Bet == state.CurrentBet,
	})
}

func (m *Match) handleCheck(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {
	uid := message.GetUserId()

	player, ok := state.Players[uid]
	if !ok || player.Status != PlayerStatusPlaying {
		return // 玩家不存在或状态无效
	}

	callAmount := state.CurrentBet - player.Bet
	if callAmount > 0 {
		// 当前还需补注，不允许 check（只能 call / fold）
		return
	}

	state.CurrentPos = m.findNextActingSeat(player.Seat, state.SeatedPlayers)
	nextPlayer := state.SeatedPlayers[state.CurrentPos]

	m.broadcast(logger, dispatcher, OpPlayerAction, &MatchDataPlayerActionS{
		ActionType:  ActionCheck,
		ActorSeat:   player.Seat,
		SpentAmount: 0,
		MainPot:     state.Pot,
		SidePot:     0,
		NextSeat:    state.CurrentPos,
		CallAmount:  state.CurrentBet - nextPlayer.Bet,
		MinRaiseTo:  m.CalcMinRaiseTo(state),
		CanCheck:    nextPlayer.Bet == state.CurrentBet,
	})
}

func (m *Match) handleFold(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {
	uid := message.GetUserId()
	player, ok := state.Players[uid]
	if !ok || player.Status != PlayerStatusPlaying {
		return // 玩家不存在或不能操作
	}

	// 设置弃牌状态
	player.Status = PlayerStatusFolded
	player.Action = ActionFold
	// TODO: - 广播消息 结算 等

	//state.CurrentPos = m.findNextActingSeat(player.Seat, state.SeatedPlayers)
}

func (m *Match) handleAllIn(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {
	uid := message.GetUserId()
	player, ok := state.Players[uid]
	if !ok || player.Status != PlayerStatusPlaying {
		return
	}

	// 玩家要 All-in 的金额 = 剩余筹码
	allInAmount := player.Chips
	if allInAmount <= 0 {
		return
	}

	// 新下注总额
	totalBet := player.Bet + allInAmount

	// 更新下注和筹码
	player.Bet = totalBet
	player.Chips = 0
	player.Status = PlayerStatusAllIn
	player.Action = ActionAllIn

	// 更新奖池
	state.Pot = allInAmount

	// 是否构成合法加注？
	raiseAmount := totalBet - state.CurrentBet
	isValidRaise := raiseAmount >= state.LastRaiseAmount
	if totalBet > state.CurrentBet && isValidRaise {
		// 合法 raise：更新最大下注和最近加注记录
		state.LastRaiseAmount = raiseAmount
		state.CurrentBet = totalBet
	}

	state.CurrentPos = m.findNextActingSeat(player.Seat, state.SeatedPlayers)
	// TODO: - 广播

}

func (m *Match) handleRaise(ctx context.Context, logger runtime.Logger, dispatcher runtime.MatchDispatcher, message runtime.MatchData, state *MatchState) {

}

func (m *Match) findNextActingSeat(startPos int64, seatedPlayers map[int64]*Player) int64 {
	return m.nextActiveSeat(startPos, 1, seatedPlayers)
}

// nextActiveSeat 返回从 startPos 顺时针数第 offset(+1, +2, …) 位有人的座位号。
// 如果桌上玩家人数不足 offset，返回 -1。
func (m *Match) nextActiveSeat(startPos int64, offset int64, seatedPlayers map[int64]*Player) int64 {
	if len(seatedPlayers) == 0 || offset <= 0 {
		return -1
	}
	var steps int64 = 0
	pos := (startPos + 1) % 9 // 从下一位开始找
	for searched := 0; searched < 9*int(offset); searched++ {
		if player, ok := seatedPlayers[pos]; ok && player.Status == PlayerStatusPlaying { // 该座位有人,并且正在玩
			steps++
			if steps == offset {
				return pos
			}
		}
		pos = (pos + 1) % 9
	}
	return -1 // 不足 offset 位玩家
}

// CalcBlindsPreFlop 返回小盲、大盲、Pre-flop 第一行动位。
// 若玩家不足 -> 对应返回 -1。
func (m *Match) CalcBlindsPreFlop(dealer int64, seatedPlayers map[int64]*Player) (sb int64, bb int64, firstToAct int64) {
	sb = m.nextActiveSeat(dealer, 1, seatedPlayers)         // dealer 左边第一活跃玩家
	bb = m.nextActiveSeat(dealer, 2, seatedPlayers)         // dealer 左边第二活跃玩家
	firstToAct = m.nextActiveSeat(dealer, 3, seatedPlayers) // Pre-flop 第三位
	return
}

func (m *Match) CalcMinRaiseTo(state *MatchState) int64 {
	// 如果是第一轮加注（还没人加注过），最小加注至少是当前最大下注 + 大盲注
	if state.LastRaiseAmount == 0 {
		return state.CurrentBet + state.BigBlind
	}

	// 否则，最小加注目标 = 当前最大下注 + 上一次合法加注幅度
	return state.CurrentBet + state.LastRaiseAmount
}

// shuffledDeck 洗牌
func (m *Match) shuffledDeck() {
	m.Deck = NewShuffledDeck()
}

// drawCards 发牌
func (m *Match) drawCards(n int) []Card {

	if len(m.Deck) < n {
		// 如果不够发，返回空（或可处理错误）
		return nil
	}
	cards := m.Deck[:n]
	m.Deck = m.Deck[n:] // 抽过的牌从牌堆中移除
	return cards
}

// broadcast 广播
func (m *Match) broadcast(logger runtime.Logger, dispatcher runtime.MatchDispatcher, opCode int64, data interface{}) {
	if data == nil {
		if err := dispatcher.BroadcastMessage(opCode, nil, nil, nil, true); err != nil {
			logger.Error(fmt.Sprintf("Broadcast error: %v", err))
		}
		return
	}
	b, err := json.Marshal(data)
	if err != nil {
		logger.Error(fmt.Sprintf("Broadcast marshal error: %v", err))
		return
	}
	if err := dispatcher.BroadcastMessage(opCode, b, nil, nil, true); err != nil {
		logger.Error(fmt.Sprintf("Broadcast error: %v", err))
	}
}

// sendToPlayers 私发
func (m *Match) sendToPlayer(logger runtime.Logger, dispatcher runtime.MatchDispatcher, player runtime.Presence, opCode int64, data interface{}) {
	b, err := json.Marshal(data)
	if err != nil {
		logger.Error(fmt.Sprintf("Broadcast marshal error: %v", err))
		return
	}
	if err := dispatcher.BroadcastMessage(opCode, b, []runtime.Presence{player}, nil, true); err != nil {
		logger.Error(fmt.Sprintf("Broadcast error: %v", err))
	}
}

func Min(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}
